package com.zoe.onelink.logging.trace.autoconfigure;

import brave.Span;
import brave.Tracer;
import brave.rpc.RpcTracing;
import brave.rpc.RpcTracingCustomizer;
import com.alibaba.druid.filter.FilterEventAdapter;
import com.alibaba.fastjson.JSON;
import com.zoe.onelink.auth.token.authenticate.OnelinkTokenAuthenticateCallback;
import com.zoe.onelink.common.constant.TraceSpanConstant;
import com.zoe.onelink.core.exception.interceptor.WebExceptionInterceptor;
import com.zoe.onelink.core.util.http.HttpClient;
import com.zoe.onelink.logging.trace.cache.SwaggerTraceApiInfoCache;
import com.zoe.onelink.logging.trace.dubbo.DubboTraceRequestParser;
import com.zoe.onelink.logging.trace.exception.TraceWebExceptionInterceptor;
import com.zoe.onelink.logging.trace.feign.FeignTraceLogInterceptor;
import com.zoe.onelink.logging.trace.feign.processor.OnelinkTracedHttpClientBeanPostProcessor;
import com.zoe.onelink.logging.trace.http.interceptor.HttpRequestTraceLogInterceptor;
import com.zoe.onelink.logging.trace.listener.OnelinkLoggingPropertyChangeListener;
import com.zoe.onelink.logging.trace.property.OnelinkZipkinProperties;
import com.zoe.onelink.logging.trace.property.TraceLogProperties;
import com.zoe.onelink.logging.trace.sql.SqlTraceLogInterceptor;
import com.zoe.onelink.logging.trace.sql.druid.OnelinkDruidSqlEventFilter;
import com.zoe.onelink.util.StrUtil;
import feign.RequestInterceptor;
import lombok.RequiredArgsConstructor;
import org.apache.ibatis.plugin.Interceptor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.rpc.TraceRpcAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;
import java.util.Map;

/**
 * <p>标题: </p>
 * <p>描述: </p>
 * <p>版权: Copyright (c) 2021</p>
 * <p>公司: 智业软件股份有限公司</p>
 *
 * @version: 1.0
 * @author: ruansheng
 * @date: 2021-08-12
 */
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({TraceLogProperties.class, OnelinkZipkinProperties.class})
@ConditionalOnClass(Tracer.class)
@RequiredArgsConstructor
public class OnelinkTraceAutoConfiguration {


    @RequiredArgsConstructor
    @Configuration(proxyBeanMethods = false)
    static class RequestFilterBeanConfiguration implements WebMvcConfigurer {

        private final TraceLogProperties traceLogProperties;
        private final Tracer tracer;

        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            TraceLogProperties.HttpLogProperties httpLogProperties = this.traceLogProperties.getHttp();
            List<String> httpExcludeUrls = httpLogProperties.getExcludeUrls();
            if (httpLogProperties.isEnabled()) {
                registry.addInterceptor(new HttpRequestTraceLogInterceptor(traceLogProperties, tracer))
                        .addPathPatterns("/**")
                        .excludePathPatterns(httpExcludeUrls);
            }
        }

    }


    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(Interceptor.class)
    static class SqlTraceConfiguration {

        @Bean
        @ConditionalOnProperty(prefix = "onelink.logging.sql", name = "enabled", matchIfMissing = true)
        public Interceptor sqlLogInterceptor(TraceLogProperties traceLogProperties, Tracer tracer) {
            return new SqlTraceLogInterceptor(traceLogProperties.getSql(), tracer);
        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(FilterEventAdapter.class)
    static class OnelinkDruidFilterAutoConfiguration {

        @Bean
        public OnelinkDruidSqlEventFilter onelinkDruidSqlEventFilter(TraceLogProperties traceLogProperties) {
            return new OnelinkDruidSqlEventFilter(traceLogProperties.getSql());
        }

    }


    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(WebExceptionInterceptor.class)
    static class WebExceptionInterceptorConfiguration {

        @Bean
        @Qualifier
        public WebExceptionInterceptor webExceptionInterceptor(Tracer tracer) {
            return new TraceWebExceptionInterceptor(tracer);
        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({HttpClient.class})
    @RequiredArgsConstructor
    @AutoConfigureAfter(TraceAutoConfiguration.class)
    static class TracedHttpClientConfiguration {

        @Bean
        static BeanPostProcessor onelinkTracedHttpClientBeanPostProcessor(BeanFactory beanFactory) {
            return new OnelinkTracedHttpClientBeanPostProcessor(beanFactory);
        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RequestInterceptor.class)
    @ConditionalOnProperty(prefix = "onelink.logging.feign", name = "enabled", matchIfMissing = true)
    static class FeignTraceAutoConfiguration {

        @Bean
        public RequestInterceptor feignTraceLogInterceptor(Tracer tracer, TraceLogProperties traceLogProperties) {
            return new FeignTraceLogInterceptor(tracer, traceLogProperties.getFeign());
        }

    }

    @Bean
    public SwaggerTraceApiInfoCache swaggerTraceApiInfoCache() {
        return new SwaggerTraceApiInfoCache();
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(name = "spring.sleuth.rpc.enabled", havingValue = "true", matchIfMissing = true)
    @ConditionalOnClass({RpcTracing.class, RpcTracingCustomizer.class})
    @AutoConfigureBefore(TraceRpcAutoConfiguration.class)
    @AutoConfigureAfter(TraceAutoConfiguration.class)
    public static class TraceDubboAutoConfiguration {

        @Bean
        public RpcTracingCustomizer dubboTracingCustomizer() {
            return builder -> {
                DubboTraceRequestParser requestParser = new DubboTraceRequestParser();
                builder.clientRequestParser(requestParser)
                        .serverRequestParser(requestParser);
            };
        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(OnelinkTokenAuthenticateCallback.class)
    public static class TraceAuthInfoAutoConfiguration {

        @Bean
        @ConditionalOnBean(Tracer.class)
        public OnelinkTokenAuthenticateCallback traceAuthCallback(Tracer tracer) {
            return new OnelinkTokenAuthenticateCallback() {
                @Override
                public void onAuthenticateSuccess(Map<String, Object> authMap) {
                    String authJson = authMap == null || authMap.isEmpty()
                            ? StrUtil.EMPTY_JSON
                            : JSON.toJSONString(authMap);
                    Span span = tracer.currentSpan();
                    span.tag(TraceSpanConstant.AUTH_INFO, authJson);
                }

                @Override
                public void onAuthenticateFailure(Throwable throwable) {
                    // ignore
                }
            };
        }

    }

    @Bean
    public SmartApplicationListener onelinkLoggingPropertyChangeListener() {
        return new OnelinkLoggingPropertyChangeListener();
    }


}
